تعمق في JavaScript Temporal Duration، الواجهة البرمجية الحديثة لإجراء حسابات ومقارنات وتنسيقات دقيقة للفترات الزمنية. تعلم كيفية إدارة الفترات الزمنية بثقة وبوعي عالمي، متجنبًا المشكلات الشائعة مع كائنات Date.
JavaScript Temporal Duration: إتقان حسابات الفترات الزمنية وتنسيقها للتطبيقات العالمية
تُعد إدارة الوقت في تطوير البرمجيات عملية معقدة بشكل سيء السمعة. فبدءًا من تتبع الجداول الزمنية للمشاريع عبر القارات إلى جدولة مؤتمرات الفيديو الدولية، يمكن أن تؤدي الفروق الدقيقة في الفترات الزمنية والمناطق الزمنية والتوقيت الصيفي إلى أخطاء برمجية دقيقة ولكنها حرجة. لعقود من الزمان، كافح مطورو جافاسكريبت مع كائن Date المدمج، وهو أداة، على الرغم من كونها وظيفية للنقاط الزمنية البسيطة، إلا أنها تقصر عندما يتعلق الأمر بالحسابات الدقيقة للفترات الزمنية وإدارة الوقت القوية والواعية عالميًا.
نقدم لكم واجهة JavaScript Temporal البرمجية – وهي مقترح ثوري مصمم لتوفير واجهة برمجية حديثة وقوية وسهلة الاستخدام للتعامل مع التواريخ والأوقات في جافاسكريبت. ومن بين أنواعها الجديدة القوية، يبرز Temporal.Duration كحل نهائي للتعامل مع الفترات الزمنية. سيأخذك هذا المقال في رحلة عميقة إلى Temporal.Duration، مستكشفًا قدراته في العمليات الحسابية والمقارنة والتنسيق الذكي، مما يضمن أن تطبيقاتك يمكنها إدارة الوقت بدقة ووضوح عالميين.
سواء كنت تبني نظامًا لوجستيًا عالميًا، أو منصة تداول مالية، أو مخطط أحداث متعدد المناطق الزمنية، فإن فهم Temporal.Duration أمر حاسم لإزالة الغموض المتعلق بالوقت وتقديم تجارب مستخدم موثوقة ومُدوّلة.
أوجه القصور في كائن `Date` بجافاسكريبت للفترات الزمنية
قبل أن نحتفل بقدوم Temporal.Duration، من الضروري فهم قيود كائن Date الحالي، خاصة عند التعامل مع الفترات الزمنية. يمثل كائن Date نقطة زمنية محددة، تُقاس بالمللي ثانية منذ حقبة يونكس (1 يناير 1970، بالتوقيت العالمي المنسق). وعلى الرغم من أنه يمكن استخدامه لإجراء عمليات حسابية أساسية، إلا أنه يحمل العديد من العيوب الكامنة التي تجعله غير مناسب لإدارة المدد الزمنية بشكل قوي:
-
قابلية التغيير (Mutability): كائنات
Dateقابلة للتغيير. أي عملية على كائنDateتغير حالته الداخلية، مما قد يؤدي إلى آثار جانبية غير متوقعة وأخطاء صعبة التتبع، خاصة في التطبيقات المعقدة أو البيئات المتزامنة.const d = new Date('2023-01-15T10:00:00Z'); const d2 = d; // d2 now references the same object as d d.setHours(d.getHours() + 1); console.log(d.toISOString()); // 2023-01-15T11:00:00.000Z console.log(d2.toISOString()); // 2023-01-15T11:00:00.000Z (d2 also changed!) -
الافتقار إلى مفهوم المدة الزمنية: لا يمتلك كائن
Dateمفهومًا مباشرًا لـ "المدة" أو "الفترة". ينتج عن حساب الفرق بين تاريخين عدد من المللي ثانية، والذي يحتاج بعد ذلك إلى تحويله يدويًا إلى سنوات وأشهر وأيام، إلخ. هذا التحويل اليدوي عرضة للأخطاء، خاصة عند التعامل مع أشهر متغيرة الطول أو سنوات كبيسة.const date1 = new Date('2023-01-01T00:00:00Z'); const date2 = new Date('2023-03-01T00:00:00Z'); const diffMs = date2.getTime() - date1.getTime(); // How many months is this? What about leap years? // (diffMs / (1000 * 60 * 60 * 24 * 30)) is an approximation at best. console.log(`Difference in milliseconds: ${diffMs}`); console.log(`Approximate days: ${diffMs / (1000 * 60 * 60 * 24)}`); // Works for days, but not robust for months/years -
غموض المنطقة الزمنية: غالبًا ما تخلط كائنات
Dateبين التوقيت المحلي والتوقيت العالمي المنسق (UTC). على الرغم من أنها تخزن داخليًا المللي ثانية بالتوقيت العالمي المنسق، إلا أن دوالها غالبًا ما تعمل على المنطقة الزمنية المحلية للنظام بشكل افتراضي، مما يؤدي إلى الارتباك وعدم الاتساق عند العمل مع أنظمة موزعة أو مستخدمين دوليين. - تحديات التوقيت الصيفي (DST): يمكن أن تتسبب انتقالات التوقيت الصيفي في أن يكون طول الأيام 23 أو 25 ساعة. قد لا تؤدي العمليات الحسابية البسيطة (مثل إضافة 24 ساعة إلى تاريخ) دائمًا إلى اليوم التقويمي التالي، مما يؤدي إلى حسابات غير صحيحة عندما يُفترض أن "اليوم" هو فترة ثابتة مدتها 24 ساعة.
هذه القيود أجبرت المطورين تاريخيًا على الاعتماد على مكتبات خارجية مثل Moment.js أو date-fns، أو كتابة تعليمات برمجية مخصصة معقدة وعرضة للخطأ للتعامل مع الفترات الزمنية بشكل صحيح. تهدف Temporal إلى جلب هذه الإمكانيات إلى جافاسكريبت بشكل أصلي.
تقديم JavaScript Temporal: نهج حديث للتعامل مع الوقت
واجهة Temporal البرمجية هي كائن عام جديد وشامل في جافاسكريبت مصمم ليكون بديلاً حديثًا لكائن Date القديم. مبادؤها الأساسية هي عدم القابلية للتغيير، والتعامل الصريح مع المناطق الزمنية، والفصل الواضح بين المفاهيم الزمنية المختلفة. تقدم Temporal عدة فئات جديدة، تمثل كل منها جانبًا مميزًا من الوقت:
Temporal.Instant: نقطة زمنية محددة لا لبس فيها، مستقلة عن أي تقويم أو منطقة زمنية (تشبه طابع يونكس الزمني، ولكن بدقة النانو ثانية).Temporal.ZonedDateTime: نقطة زمنية محددة في تقويم ومنطقة زمنية معينة. هذا هو التمثيل الأكثر اكتمالاً لتاريخ ووقت محددين للمستخدم.Temporal.PlainDate: تاريخ تقويمي (سنة، شهر، يوم) بدون وقت أو منطقة زمنية.Temporal.PlainTime: وقت ساعة حائط (ساعة، دقيقة، ثانية، إلخ) بدون تاريخ أو منطقة زمنية.Temporal.PlainDateTime: تاريخ تقويمي ووقت ساعة حائط معًا، بدون منطقة زمنية.Temporal.PlainYearMonth: سنة وشهر محددين في نظام تقويم.Temporal.PlainMonthDay: شهر ويوم محددين في نظام تقويم.Temporal.Duration: مدة زمنية موقعة، مثل "5 ساعات و 30 دقيقة" أو "يومان". هذا هو محور تركيزنا في هذا الدليل.
جميع كائنات Temporal غير قابلة للتغيير، مما يعني أن عمليات مثل إضافة أو طرح الوقت تنشئ كائنات جديدة بدلاً من تعديل الكائنات الموجودة، مما يعزز القدرة على التنبؤ ويقلل من الأخطاء.
فهم Temporal.Duration
يمثل Temporal.Duration مدة زمنية. والأهم من ذلك، أنه مستقل عن نقطة بداية أو نهاية محددة. إنه ببساطة "مقدار الوقت" الذي انقضى أو سينقضي. يمكن أن يتكون من سنوات وأشهر وأسابيع وأيام وساعات ودقائق وثواني ومللي ثانية وميكرو ثانية ونانو ثانية. كل مكون هو عدد صحيح، ويمكن أن يكون موجبًا أو سالبًا.
على سبيل المثال، "ساعتان و 30 دقيقة" هي مدة زمنية. "الفترة من 1 يناير إلى 1 مارس" هي مدة زمنية بين نقطتين محددتين، والتي يمكن *تمثيلها* بواسطة Temporal.Duration، لكن Duration نفسها هي مجرد الفاصل الزمني.
إنشاء مدة زمنية
هناك عدة طرق مباشرة لإنشاء كائنات Temporal.Duration:
1. استخدام المُنشئ (Constructor)
يسمح لك المُنشئ بتحديد كل مكون مباشرة. لاحظ أن الوسائط مرتبة من أكبر وحدة (سنوات) إلى أصغرها (نانو ثانية).
// new Temporal.Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds)
// A duration of 2 hours and 30 minutes
const duration1 = new Temporal.Duration(0, 0, 0, 0, 2, 30, 0, 0, 0, 0);
console.log(duration1.toString()); // P2H30M
// A duration of 1 year, 2 months, 3 days
const duration2 = new Temporal.Duration(1, 2, 0, 3);
console.log(duration2.toString()); // P1Y2M3D
// A duration of -5 days
const duration3 = new Temporal.Duration(0, 0, 0, -5);
console.log(duration3.toString()); // P-5D
2. استخدام Temporal.Duration.from() مع كائن
غالبًا ما تكون هذه هي الطريقة الأكثر قابلية للقراءة لإنشاء مدد زمنية، مما يسمح لك بتحديد المكونات التي تحتاجها فقط.
// Duration of 1.5 hours
const halfHourDuration = Temporal.Duration.from({ hours: 1, minutes: 30 });
console.log(halfHourDuration.toString()); // P1H30M
// Duration of 7 days (1 week)
const oneWeekDuration = Temporal.Duration.from({ days: 7 });
console.log(oneWeekDuration.toString()); // P7D
// Duration with fractional seconds (e.g., 2.5 seconds)
const twoPointFiveSeconds = Temporal.Duration.from({ seconds: 2, milliseconds: 500 });
console.log(twoPointFiveSeconds.toString()); // PT2.5S
// Negative duration
const negativeDuration = Temporal.Duration.from({ minutes: -45 });
console.log(negativeDuration.toString()); // PT-45M
3. استخدام Temporal.Duration.from() مع سلسلة ISO 8601
تستفيد Temporal من تنسيق المدة الزمنية ISO 8601، وهو معيار لتمثيل المدد الزمنية. هذا ممتاز لتحليل المدد الزمنية من مصادر بيانات خارجية.
يبدو التنسيق بشكل عام مثل P[years]Y[months]M[weeks]W[days]DT[hours]H[minutes]M[seconds]S. يفصل الحرف T مكونات التاريخ عن مكونات الوقت.
// 1 year, 2 months, 3 days
const isoDuration1 = Temporal.Duration.from('P1Y2M3D');
console.log(isoDuration1.toString()); // P1Y2M3D
// 4 hours, 5 minutes, 6 seconds
const isoDuration2 = Temporal.Duration.from('PT4H5M6S');
console.log(isoDuration2.toString()); // PT4H5M6S
// A combined duration
const isoDuration3 = Temporal.Duration.from('P7DT12H30M');
console.log(isoDuration3.toString()); // P7DT12H30M
// Fractional seconds are also supported
const isoDuration4 = Temporal.Duration.from('PT1.5S');
console.log(isoDuration4.toString()); // PT1.5S
إجراء العمليات الحسابية على المدد الزمنية
تتألق القوة الحقيقية لـ Temporal.Duration في قدراتها الحسابية. يمكنك جمع وطرح وضرب وتقسيم المدد الزمنية، وكذلك إضافتها/طرحها من أنواع Temporal الأخرى للتاريخ والوقت. جميع العمليات تُرجع كائنات Temporal.Duration جديدة بسبب عدم القابلية للتغيير.
جمع المدد الزمنية
تجمع دالة add() بين مدتين زمنيتين.
const sprintDuration = Temporal.Duration.from({ weeks: 2 });
const bufferDuration = Temporal.Duration.from({ days: 3 });
const totalProjectTime = sprintDuration.add(bufferDuration);
console.log(totalProjectTime.toString()); // P2W3D (or P17D if normalized later)
// Adding a negative duration is equivalent to subtraction
const result = Temporal.Duration.from({ hours: 5 }).add({ hours: -2 });
console.log(result.toString()); // PT3H
يمكنك أيضًا إضافة مدة زمنية إلى أي كائن تاريخ/وقت من Temporal. هنا يحدث السحر، حيث تتعامل Temporal بشكل صحيح مع تحولات المنطقة الزمنية وانتقالات التوقيت الصيفي عند الحاجة.
const projectStart = Temporal.PlainDateTime.from('2023-10-26T09:00:00');
const projectDuration = Temporal.Duration.from({ days: 10, hours: 4 });
const projectEnd = projectStart.add(projectDuration);
console.log(projectEnd.toString()); // 2023-11-05T13:00:00
// With a ZonedDateTime, the time zone rules are applied correctly
const meetingStartUTC = Temporal.ZonedDateTime.from('2024-03-09T14:00:00[UTC]');
const meetingDuration = Temporal.Duration.from({ hours: 1, minutes: 45 });
const meetingEndUTC = meetingStartUTC.add(meetingDuration);
console.log(meetingEndUTC.toString()); // 2024-03-09T15:45:00+00:00[UTC]
// Example crossing a DST boundary (assuming 'Europe/Berlin' shifts at 03:00 on 2024-03-31)
const springForwardStart = Temporal.ZonedDateTime.from('2024-03-30T22:00:00[Europe/Berlin]');
const twentyFourHours = Temporal.Duration.from({ hours: 24 });
const nextDay = springForwardStart.add(twentyFourHours); // Adds 24 actual hours
console.log(springForwardStart.toString()); // 2024-03-30T22:00:00+01:00[Europe/Berlin]
console.log(nextDay.toString()); // 2024-03-31T23:00:00+02:00[Europe/Berlin] (Local time skipped an hour)
لاحظ كيف أن إضافة 24 ساعة إلى 2024-03-30T22:00:00 في برلين (وهي UTC+1) ينتج عنها 2024-03-31T23:00:00 (الآن UTC+2). قفزت الساعة إلى الأمام بمقدار ساعة واحدة، لذا فإن وقت الساعة الجدارية أصبح متأخرًا بساعة واحدة في نفس التاريخ مقارنة بوقت الساعة الجدارية الابتدائي. هذا يوضح بدقة وعي Temporal بالمنطقة الزمنية والتوقيت الصيفي عند إجراء العمليات الحسابية على `ZonedDateTime`.
طرح المدد الزمنية
تعمل دالة subtract() بشكل مشابه لـ add()، لكنها تزيل الوقت.
const deadlineDuration = Temporal.Duration.from({ days: 30 });
const gracePeriod = Temporal.Duration.from({ days: 5 });
const effectiveDeadline = deadlineDuration.subtract(gracePeriod);
console.log(effectiveDeadline.toString()); // P25D
const taskEnd = Temporal.PlainDateTime.from('2023-12-01T17:00:00');
const taskDuration = Temporal.Duration.from({ hours: 8, minutes: 30 });
const taskStart = taskEnd.subtract(taskDuration);
console.log(taskStart.toString()); // 2023-12-01T08:30:00
ضرب وتقسيم المدد الزمنية
تقوم دالتا multiply() و divide() بتغيير مقياس مكونات المدة الزمنية بعامل معين. هذا مفيد لسيناريوهات مثل حساب الوقت الإجمالي لتكرارات متعددة لمهمة ما.
const trainingSession = Temporal.Duration.from({ minutes: 45 });
const weeklyTraining = trainingSession.multiply(5); // Five sessions a week
console.log(weeklyTraining.toString()); // PT225M
const totalProjectHours = Temporal.Duration.from({ hours: 160 });
const teamMembers = 4;
const hoursPerMember = totalProjectHours.divide(teamMembers);
console.log(hoursPerMember.toString()); // PT40H
عكس إشارة المدد الزمنية
تعكس دالة negate() إشارة جميع مكونات المدة الزمنية. تصبح المدة الموجبة سالبة، والعكس صحيح.
const delayDuration = Temporal.Duration.from({ hours: 3 });
const advanceDuration = delayDuration.negate();
console.log(delayDuration.toString()); // PT3H
console.log(advanceDuration.toString()); // PT-3H
القيمة المطلقة للمدد الزمنية
تُرجع دالة abs() كائن Temporal.Duration جديدًا مع جعل جميع المكونات موجبة، مما يمنحك بشكل فعال حجم المدة بغض النظر عن إشارتها.
const negativeDelay = Temporal.Duration.from({ minutes: -60 });
const positiveDuration = negativeDelay.abs();
console.log(negativeDelay.toString()); // PT-60M
console.log(positiveDuration.toString()); // PT60M
مقارنة وتسوية المدد الزمنية
يمكن أن تكون مقارنة المدد الزمنية صعبة، خاصة عندما تكون هناك وحدات مختلفة (على سبيل المثال، هل شهر واحد يساوي 30 يومًا؟). توفر Temporal أدوات للمقارنة والتسوية للتعامل مع هذه التعقيدات.
مقارنة المدد الزمنية باستخدام compare()
تُرجع الدالة الثابتة Temporal.Duration.compare(duration1, duration2, options):
-1إذا كانتduration1أقل منduration20إذا كانتduration1تساويduration21إذا كانتduration1أكبر منduration2
بشكل حاسم، عند مقارنة المدد الزمنية التي تشمل وحدات متغيرة الطول مثل السنوات أو الأشهر أو الأسابيع، غالبًا ما تحتاج إلى توفير خيار relativeTo. هذا المعامل هو كائن `Temporal.ZonedDateTime` أو `Temporal.PlainDateTime` يوفر سياقًا لكيفية تفسير هذه الوحدات (على سبيل المثال، عدد الأيام في شهر أو سنة معينة).
const oneHour = Temporal.Duration.from({ hours: 1 });
const sixtyMinutes = Temporal.Duration.from({ minutes: 60 });
console.log(Temporal.Duration.compare(oneHour, sixtyMinutes)); // 0 (They are equivalent)
const oneMonth = Temporal.Duration.from({ months: 1 });
const thirtyDays = Temporal.Duration.from({ days: 30 });
// Without relativeTo, month/year comparisons are difficult
console.log(Temporal.Duration.compare(oneMonth, thirtyDays)); // 0 (Temporal makes a reasonable guess without context, often based on average)
// With relativeTo, the comparison is precise based on the context's calendar
const startOfJanuary = Temporal.PlainDate.from('2023-01-01');
const endOfFebruaryLeap = Temporal.PlainDate.from('2024-02-01'); // Leap year
// In January 2023, 1 month is 31 days
const comparisonJan = Temporal.Duration.compare(oneMonth, thirtyDays, { relativeTo: startOfJanuary });
console.log(`1 month vs 30 days in Jan 2023: ${comparisonJan}`); // 1 (1 month > 30 days)
// In February 2024 (leap year), 1 month is 29 days
const comparisonFeb = Temporal.Duration.compare(oneMonth, thirtyDays, { relativeTo: endOfFebruaryLeap });
console.log(`1 month vs 30 days in Feb 2024: ${comparisonFeb}`); // -1 (1 month < 30 days)
تسوية المدد الزمنية باستخدام normalize() و round()
يمكن تمثيل المدد الزمنية بعدة طرق (على سبيل المثال، 90 دقيقة أو ساعة و 30 دقيقة). تساعد التسوية والتقريب في توحيد هذه التمثيلات للاتساق والعرض.
normalize()
تبسط دالة normalize() مكونات المدة حيثما أمكن (على سبيل المثال، 60 دقيقة تصبح ساعة واحدة، 24 ساعة تصبح يومًا واحدًا، بشرط أن يسمح سياق `relativeTo` إذا كانت الأشهر/السنوات متضمنة).
const longMinutes = Temporal.Duration.from({ minutes: 90 });
console.log(longMinutes.toString()); // PT90M
console.log(longMinutes.normalize().toString()); // PT1H30M
const multipleDays = Temporal.Duration.from({ hours: 48 });
console.log(multipleDays.toString()); // PT48H
console.log(multipleDays.normalize().toString()); // P2D
round()
تعتبر دالة round() أكثر قوة لتحويل وتقريب المدد الزمنية إلى وحدات محددة. تأخذ كائن خيارات يحتوي على:
largestUnit: أكبر وحدة لتضمينها في الإخراج (مثل 'years', 'days', 'hours').smallestUnit: أصغر وحدة لتضمينها في الإخراج (مثل 'minutes', 'seconds', 'milliseconds').roundingIncrement: عدد صحيح لتقريب أصغر وحدة به (مثل 5 للتقريب إلى أقرب 5 دقائق).roundingMode: كيفية التعامل مع حالات التعادل (مثل 'halfExpand', 'trunc', 'ceil', 'floor').relativeTo: مطلوب لتقريب المدد الزمنية التي تحتوي على سنوات أو أشهر أو أسابيع.
const complexDuration = Temporal.Duration.from({ hours: 2, minutes: 45, seconds: 30 });
// Round to the nearest hour
const roundedToHours = complexDuration.round({ smallestUnit: 'hour' });
console.log(roundedToHours.toString()); // PT3H
// Round to the nearest 30 minutes, keeping hours
const roundedTo30Minutes = complexDuration.round({
largestUnit: 'hour',
smallestUnit: 'minute',
roundingIncrement: 30
});
console.log(roundedTo30Minutes.toString()); // PT3H
const preciseDuration = Temporal.Duration.from({ minutes: 123, seconds: 45 });
// Display as hours and minutes
const formattedDuration = preciseDuration.round({ largestUnit: 'hour', smallestUnit: 'minute' });
console.log(formattedDuration.toString()); // PT2H4M
// Rounding with months/years requires relativeTo
const longTermDuration = Temporal.Duration.from({ months: 1, days: 10 });
const referenceDate = Temporal.PlainDate.from('2023-01-15');
// Round to months, relative to a date
const roundedToMonths = longTermDuration.round({ largestUnit: 'month', smallestUnit: 'month', relativeTo: referenceDate });
console.log(roundedToMonths.toString()); // P1M
حساب المدد الزمنية بين كائنات Temporal
أحد أكثر استخدامات المدد الزمنية شيوعًا هو حساب الفاصل الزمني بين نقطتين زمنيتين محددتين. توفر Temporal دالتي until() و since() على كائنات التاريخ والوقت الخاصة بها لهذا الغرض.
دالة until()
تحسب دالة until() المدة من الكائن المُستقبِل إلى الكائن الوسيط. تكون شاملة لنقطة البداية وغير شاملة لنقطة النهاية. تأخذ كائن خيارات مشابهًا لـ round() لتحديد الوحدات المطلوبة وسلوك التقريب.
const startDate = Temporal.PlainDate.from('2023-01-01');
const endDate = Temporal.PlainDate.from('2023-03-15');
// Duration in largest possible units (months, then days)
const projectLength = startDate.until(endDate);
console.log(projectLength.toString()); // P2M14D
// Duration purely in days
const totalDays = startDate.until(endDate, { largestUnit: 'day' });
console.log(totalDays.toString()); // P73D
// Duration between two specific times, respecting time zones
const meetingStart = Temporal.ZonedDateTime.from('2024-07-20T10:00:00[America/New_York]');
const meetingEnd = Temporal.ZonedDateTime.from('2024-07-20T11:30:00[America/New_York]');
const elapsedMeetingTime = meetingStart.until(meetingEnd, { largestUnit: 'hour', smallestUnit: 'minute' });
console.log(elapsedMeetingTime.toString()); // PT1H30M
// Cross-timezone duration (from NYC to London)
const nyStartTime = Temporal.ZonedDateTime.from('2024-08-01T09:00:00[America/New_York]');
const londonEndTime = Temporal.ZonedDateTime.from('2024-08-01T17:00:00[Europe/London]');
const travelDuration = nyStartTime.until(londonEndTime);
console.log(travelDuration.toString()); // PT13H (Actual elapsed time, not wall-clock difference)
المثال الأخير مفيد بشكل خاص. على الرغم من أن نيويورك متأخرة عن لندن بـ 5 ساعات، وأن أوقات الساعة الجدارية هي 9 صباحًا و 5 مساءً في نفس اليوم، إلا أن دالة until() تحسب بشكل صحيح الوقت الفعلي المنقضي وهو 13 ساعة. هذا لأن ZonedDateTime يتعامل ضمنيًا مع فرق المنطقة الزمنية.
دالة since()
دالة since() هي عكس until(). تحسب المدة من الكائن الوسيط إلى الكائن المُستقبِل، مما ينتج عنه مدة سالبة إذا كان الوسيط في المستقبل بالنسبة للمُستقبِل.
const currentDateTime = Temporal.ZonedDateTime.from('2024-06-15T12:00:00[Europe/Paris]');
const historicEvent = Temporal.ZonedDateTime.from('2024-01-01T00:00:00[Europe/Paris]');
const timeSinceEvent = currentDateTime.since(historicEvent, { largestUnit: 'month', smallestUnit: 'day' });
console.log(timeSinceEvent.toString()); // P5M14D
const futureDate = Temporal.PlainDate.from('2025-01-01');
const pastDate = Temporal.PlainDate.from('2024-01-01');
const durationFromFuture = pastDate.since(futureDate);
console.log(durationFromFuture.toString()); // P-1Y
التعامل مع الوحدات المختلفة والتقريب للمدد المحسوبة
عند حساب المدد الزمنية، غالبًا ما يكون من الضروري تحديد `largestUnit` و `smallestUnit` للحصول على تمثيل يمكن قراءته من قبل الإنسان، خاصة بالنسبة للعمر أو الوقت المنقضي أو العد التنازلي.
const birthDate = Temporal.PlainDate.from('1990-07-15');
const today = Temporal.PlainDate.from('2024-06-15');
// Calculate age in years, months, and days
const age = birthDate.until(today, { largestUnit: 'year', smallestUnit: 'day' });
console.log(`Age: ${age.years} years, ${age.months} months, ${age.days} days`); // Age: 33 years, 11 months, 0 days
// Calculate time remaining for a task in hours and minutes
const now = Temporal.Instant.fromEpochSeconds(Date.now() / 1000);
const deadline = Temporal.Instant.from('2024-07-01T09:00:00Z');
const timeLeft = now.until(deadline, { largestUnit: 'hour', smallestUnit: 'minute', roundingMode: 'ceil' });
console.log(`Time left: ${timeLeft.hours} hours and ${timeLeft.minutes} minutes.`); // Example: Time left: 355 hours and 38 minutes.
تنسيق المدد الزمنية للجماهير العالمية
على الرغم من أن Temporal.Duration يوفر تمثيلات برمجية دقيقة للفترات الزمنية، إلا أنه لا يحتوي على دالة toLocaleString() مدمجة. هذا مقصود: المدد الزمنية هي أطوال زمنية مجردة، ويمكن أن يختلف عرضها بشكل كبير اعتمادًا على السياق واللغة ومستوى التفاصيل المطلوب. أنت، بصفتك المطور، مسؤول عن تقديم المدد الزمنية بطريقة سهلة الاستخدام ومفهومة عالميًا.
تمثيل سلسلة ISO 8601
تُرجع دالة toString() الافتراضية لكائن Temporal.Duration تمثيلها كسلسلة ISO 8601. هذا ممتاز للتواصل بين الآلات، والتسلسل، والتخزين، ولكنه نادرًا ما يكون مناسبًا للعرض المباشر للمستخدمين النهائيين.
const examDuration = Temporal.Duration.from({ hours: 2, minutes: 15 });
console.log(examDuration.toString()); // PT2H15M
const holidayDuration = Temporal.Duration.from({ weeks: 2, days: 3 });
console.log(holidayDuration.toString()); // P2W3D
التنسيق اليدوي للقراءة والتدويل
للعرض المواجه للمستخدم، ستقوم عادةً باستخراج مكونات المدة وتنسيقها باستخدام استيفاء السلاسل وواجهة Intl API في جافاسكريبت.
إليك مثال لدالة مخصصة تقوم بتنسيق مدة زمنية:
function formatDurationToHumanReadable(duration, locale = 'en-US') {
const parts = [];
// Using Intl.NumberFormat for locale-aware number formatting
const numberFormatter = new Intl.NumberFormat(locale);
if (duration.years !== 0) {
parts.push(numberFormatter.format(duration.years) + ' ' + (duration.years === 1 ? 'year' : 'years'));
}
if (duration.months !== 0) {
parts.push(numberFormatter.format(duration.months) + ' ' + (duration.months === 1 ? 'month' : 'months'));
}
if (duration.weeks !== 0) {
parts.push(numberFormatter.format(duration.weeks) + ' ' + (duration.weeks === 1 ? 'week' : 'weeks'));
}
if (duration.days !== 0) {
parts.push(numberFormatter.format(duration.days) + ' ' + (duration.days === 1 ? 'day' : 'days'));
}
if (duration.hours !== 0) {
parts.push(numberFormatter.format(duration.hours) + ' ' + (duration.hours === 1 ? 'hour' : 'hours'));
}
if (duration.minutes !== 0) {
parts.push(numberFormatter.format(duration.minutes) + ' ' + (duration.minutes === 1 ? 'minute' : 'minutes'));
}
if (duration.seconds !== 0) {
// Round seconds for display if they have fractional parts
const roundedSeconds = numberFormatter.format(duration.seconds.toFixed(0)); // Or toFixed(1) for one decimal
parts.push(roundedSeconds + ' ' + (duration.seconds === 1 ? 'second' : 'seconds'));
}
if (parts.length === 0) {
// Handle cases where the duration is zero or very small (e.g., nanoseconds only)
if (duration.milliseconds !== 0 || duration.microseconds !== 0 || duration.nanoseconds !== 0) {
const totalMs = duration.milliseconds + duration.microseconds / 1000 + duration.nanoseconds / 1_000_000;
return numberFormatter.format(totalMs.toFixed(2)) + ' milliseconds';
}
return '0 seconds';
}
// Join parts with comma and 'and' for the last part (basic English joining)
if (parts.length > 1) {
const lastPart = parts.pop();
return parts.join(', ') + ' and ' + lastPart;
} else {
return parts[0];
}
}
const tripDuration = Temporal.Duration.from({ days: 3, hours: 10, minutes: 45 });
console.log(formatDurationToHumanReadable(tripDuration, 'en-US')); // 3 days, 10 hours and 45 minutes
console.log(formatDurationToHumanReadable(tripDuration, 'es-ES')); // 3 días, 10 horas y 45 minutos (example of Intl.NumberFormat)
const meetingReminder = Temporal.Duration.from({ minutes: 5 });
console.log(formatDurationToHumanReadable(meetingReminder, 'en-GB')); // 5 minutes
const microDuration = Temporal.Duration.from({ nanoseconds: 1234567 });
console.log(formatDurationToHumanReadable(microDuration, 'en-US')); // 1.23 milliseconds
لمزيد من صيغ الجمع المتقدمة وتنسيق القوائم المترجمة، يمكنك دمج هذا مع Intl.RelativeTimeFormat أو مكتبات قوالب سلاسل أكثر تعقيدًا. المفتاح هو فصل حساب المدة (الذي تتولاه Temporal) عن عرضها (الذي يتولاه منطق التنسيق الخاص بك و Intl).
استخدام Intl.DurationFormat (مستقبلي/مقترح)
هناك مقترح TC39 مستمر لـ Intl.DurationFormat، والذي يهدف إلى توفير طريقة أصلية وواعية باللغة لتنسيق المدد الزمنية. إذا تم توحيده وتنفيذه، فسيبسط بشكل كبير التنسيق اليدوي الموضح أعلاه، ويقدم حلاً قويًا للتدويل مباشرة داخل نظام جافاسكريبت البيئي.
من المرجح أن يعمل بشكل مشابه لكائنات Intl الأخرى:
// This is hypothetical and based on the proposal's current state
// Check browser compatibility before using in production
/*
const duration = Temporal.Duration.from({ days: 1, hours: 2, minutes: 30 });
const formatter = new Intl.DurationFormat('en-US', {
style: 'long',
years: 'long',
days: 'long',
hours: 'long',
minutes: 'long',
});
console.log(formatter.format(duration)); // "1 day, 2 hours, 30 minutes"
const shortFormatter = new Intl.DurationFormat('fr-FR', { style: 'short', hours: 'numeric', minutes: 'numeric' });
console.log(shortFormatter.format(duration)); // "1 j, 2 h, 30 min"
*/
على الرغم من أنه غير متاح بعد في جميع البيئات، راقب هذا المقترح لأنه يعد بأن يكون الحل النهائي لتنسيق المدد الزمنية عالميًا في المستقبل.
حالات الاستخدام العملية والاعتبارات العالمية
Temporal.Duration ليس مجرد تحسين أكاديمي؛ إنه يحل مشاكل العالم الحقيقي في التطبيقات التي تعمل عبر مناطق زمنية وثقافات مختلفة.
1. الجدولة وإدارة الأحداث
بالنسبة للمنصات التي تدير الأحداث الدولية أو المؤتمرات أو المواعيد، يعد حساب مدد الأحداث أو الوقت المتبقي حتى الحدث أو مدة الاستراحة أمرًا بالغ الأهمية. يضمن Temporal.Duration أن هذه الحسابات دقيقة بغض النظر عن موقع المستخدم أو قواعد المنطقة الزمنية المحلية.
// Calculate remaining time for a global webinar
const now = Temporal.ZonedDateTime.from('2024-07-25T10:00:00[Europe/London]'); // Example current time
const webinarStart = Temporal.ZonedDateTime.from('2024-07-26T14:30:00[Asia/Tokyo]');
const timeUntilWebinar = now.until(webinarStart, {
largestUnit: 'hour',
smallestUnit: 'minute',
roundingMode: 'ceil' // Round up to ensure users don't miss it
});
console.log(`Webinar starts in ${timeUntilWebinar.hours} hours and ${timeUntilWebinar.minutes} minutes.`);
// Output will be accurate based on actual time elapsed between these two ZonedDateTimes.
2. مقاييس الأداء والتسجيل
يتطلب قياس وقت تنفيذ العمليات أو أوقات استجابة واجهة برمجة التطبيقات أو مدد مهام الدُفعات دقة عالية. يعد Temporal.Duration، خاصة عند دمجه مع Temporal.Instant (الذي يوفر دقة النانو ثانية)، مثاليًا لهذا الغرض.
const startTime = Temporal.Instant.now();
// Simulate a complex operation
for (let i = 0; i < 1_000_000; i++) { Math.sqrt(i); }
const endTime = Temporal.Instant.now();
const executionDuration = startTime.until(endTime);
// Format to seconds with milliseconds for display
const formattedExecution = executionDuration.round({ smallestUnit: 'millisecond', largestUnit: 'second' });
console.log(`Operation took ${formattedExecution.seconds}.${String(formattedExecution.milliseconds).padStart(3, '0')} seconds.`);
3. الحسابات المالية
في مجال التمويل، تعتبر الفترات الزمنية الدقيقة ذات أهمية قصوى لحساب الفائدة أو شروط القروض أو فترات الاستثمار. يجب أن يكون العدد الدقيق للأيام أو الأشهر أو السنوات في فترة ما دقيقًا، خاصة عند التعامل مع السنوات الكبيسة أو أطوال الأشهر المحددة.
const loanStartDate = Temporal.PlainDate.from('2023-04-01');
const loanEndDate = Temporal.PlainDate.from('2028-03-31');
const loanTerm = loanStartDate.until(loanEndDate, { largestUnit: 'year', smallestUnit: 'month' });
console.log(`Loan term: ${loanTerm.years} years and ${loanTerm.months} months.`); // 4 years and 11 months
4. إعادة النظر في تحديات التدويل
-
المناطق الزمنية والتوقيت الصيفي:
Temporal.Durationبحد ذاته لا علاقة له بالمنطقة الزمنية؛ إنه يمثل فترة زمنية ثابتة. ومع ذلك، عند إضافة أو طرحDurationمن/إلىZonedDateTime، تطبق Temporal بشكل صحيح قواعد المنطقة الزمنية، بما في ذلك تحولات التوقيت الصيفي. هذا يضمن أن "مدة 24 ساعة" تقدم فعلاًZonedDateTimeبمقدار 24 ساعة فعلية، حتى لو كان ذلك يعني عبور حدود التوقيت الصيفي التي تجعل يوم *الساعة الجدارية* أقصر أو أطول. هذا الاتساق هو فوز كبير على `Date`. -
الاختلافات الثقافية: تعبر الثقافات المختلفة عن المدد الزمنية بشكل مختلف. بينما يوفر
Temporal.Durationالمكونات الخام (السنوات، الأشهر، الأيام، إلخ)، تقع على عاتقك مسؤولية استخدام واجهات `Intl` البرمجية أو منطق مخصص لتقديمها بطريقة تتناسب مع لغة المستخدم. على سبيل المثال، قد تعبر بعض الثقافات عن "ساعة و 30 دقيقة" بـ "90 دقيقة" أو تستخدم قواعد جمع مختلفة. -
أنظمة التقويم: تدعم Temporal أيضًا أنظمة تقويم مختلفة (مثل التقويمات اليابانية والفارسية والإسلامية). على الرغم من أن
Durationنفسه لا علاقة له بالتقويم، إلا أنه عندما يتفاعل مع أنواع واعية بالتقويم مثلPlainDateأوZonedDateTime، فإن العمليات الحسابية تحترم قواعد التقويم المحدد (مثل عدد الأيام في الشهر، قواعد السنة الكبيسة داخل ذلك التقويم). هذا أمر حاسم للتطبيقات العالمية التي قد تحتاج إلى عرض التواريخ في تقويمات غير ميلادية.
الوضع الحالي لـ Temporal وتبنيه
اعتبارًا من أواخر عام 2023 / أوائل عام 2024، تعد واجهة JavaScript Temporal البرمجية مقترحًا من المرحلة 3 في TC39. هذا يعني أن المواصفات مستقرة إلى حد كبير، وتخضع للتنفيذ والاختبار في مختلف محركات جافاسكريبت والمتصفحات. تعمل المتصفحات الرئيسية مثل Chrome و Firefox و Safari بنشاط على تنفيذ Temporal، مع توفر علامات تجريبية أو إصدارات مبكرة بالفعل.
على الرغم من أنها ليست متاحة عالميًا بعد بدون polyfill، إلا أن مرحلتها المتقدمة تشير إلى أنه من المرجح جدًا أن تصبح جزءًا قياسيًا من جافاسكريبت. يمكن للمطورين البدء في تجربة Temporal اليوم باستخدام polyfills أو عن طريق تمكين الميزات التجريبية في بيئات تطوير المتصفح الخاصة بهم. يتيح لك التبني المبكر أن تكون سباقًا، وتفهم فوائدها، وتدمجها في مشاريعك مع طرح دعم المتصفح.
سيؤدي تبنيها على نطاق واسع في نهاية المطاف إلى تقليل الحاجة إلى مكتبات التاريخ/الوقت الخارجية بشكل كبير، مما يوفر حلاً أصليًا قويًا لإدارة الوقت في جافاسكريبت.
أفضل الممارسات لاستخدام Temporal Duration
لتحقيق أقصى استفادة من Temporal.Duration في تطبيقاتك، ضع في اعتبارك هذه الممارسات الأفضل:
-
فضل استخدام
Temporal.Duration.from(): عند إنشاء مدد زمنية من مكونات معروفة أو سلاسل ISO، غالبًا ما تكونTemporal.Duration.from()مع كائن حرفي أكثر قابلية للقراءة وأقل عرضة للخطأ من الوسائط الموضعية للمُنشئ. -
استخدم
relativeToللمقارنات الغامضة: قدم دائمًا خيارrelativeToعند مقارنة أو تقريب المدد الزمنية التي تحتوي على سنوات أو أشهر أو أسابيع. هذا يضمن حسابات دقيقة من خلال توفير سياق التقويم اللازم. -
استفد من
until()وsince(): لحساب الفاصل الزمني بين نقطتين زمنيتين محددتين، فضل استخدام دالتيuntil()وsince()على كائنات التاريخ والوقت في Temporal. فهي تتعامل مع تعقيدات المناطق الزمنية والتوقيت الصيفي بشكل صحيح. -
قم بالتسوية والتقريب للعرض: قبل تقديم المدد الزمنية للمستخدمين، فكر في استخدام
normalize()وخاصةround()لتحويل المدة إلى الوحدات الأكثر ملاءمة وفهمًا (على سبيل المثال، تحويل 90 دقيقة إلى "ساعة و 30 دقيقة"). -
افصل بين التمثيل الداخلي والعرض: حافظ على دقة حسابات المدة الداخلية باستخدام
Temporal.Duration. قم فقط بالتحويل والتنسيق للعرض عند تقديم واجهة المستخدم، باستخدام دوال تنسيق مخصصة وواجهةIntlالبرمجية للدقة العالمية. - ابق على اطلاع: راقب تقدم واجهة Temporal البرمجية وتوافق المتصفحات. مع اقترابها من التوحيد الكامل، سيتطور النظام البيئي، وقد تظهر أدوات أو ممارسات أفضل جديدة.
- كن واعيًا بالدقة: على الرغم من أن Temporal تدعم دقة النانو ثانية، استخدم فقط الدقة التي تحتاجها حقًا. يمكن أن تجعل الدقة العالية أحيانًا تصحيح الأخطاء أصعب أو تؤدي إلى تقريب غير متوقع عند التحويل إلى عروض ذات دقة أقل.
الخاتمة
يمثل إدخال Temporal.Duration قفزة كبيرة إلى الأمام لمطوري جافاسكريبت الذين يتعاملون مع حسابات الفترات الزمنية. من خلال توفير واجهة برمجية غير قابلة للتغيير وصريحة وواعية عالميًا، تعالج Temporal القيود طويلة الأمد لكائن Date القديم.
يمكنك الآن بثقة إجراء حسابات زمنية معقدة، وقياس الفترات بدقة، وتقديم المدد الزمنية بطريقة دقيقة ومناسبة ثقافيًا للمستخدمين في جميع أنحاء العالم. سواء كنت تحسب مدة دورة إصدار برنامج، أو الوقت المتبقي حتى إطلاق منتج عالمي، أو العمر الدقيق لشخص ما، فإن Temporal.Duration يقدم الأدوات التي تحتاجها لبناء تطبيقات قوية وموثوقة.
احتضن Temporal.Duration وقم بتحويل كيفية إدارة الوقت في مشاريع جافاسكريبت الخاصة بك. مستقبل التعامل مع التاريخ والوقت في جافاسكريبت هنا، واعدًا بتجربة تطوير أكثر قابلية للتنبؤ وقوة وتوافقًا عالميًا.